package com.hisense.ovcloud.mqtt.warehouse.service;


import cn.hutool.json.JSONUtil;
import com.hisense.ovcloud.commons.configs.redis.RedisDao;
import com.hisense.ovcloud.commons.dto.common.AuthMessage;
import com.hisense.ovcloud.commons.dto.common.DeviceEndpointName;
import com.hisense.ovcloud.commons.dto.common.WifiOnlineInRedisDto;
import com.hisense.ovcloud.commons.dto.req.GetPublicKeyReq;
import com.hisense.ovcloud.commons.dto.req.NotifyWifiOnlineToHitachiReq;
import com.hisense.ovcloud.commons.dto.req.UpdateDeviceStatusReq;
import com.hisense.ovcloud.mqtt.warehouse.Constants;
import io.micrometer.common.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.HmacUtils;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Service
@Slf4j
public class AuthService {
    private final RedisDao redisDao;
    public final WebClient webClient;

    private final DevicesService devicesService;

    @Value("${busineseNotifyBaseUrl:http://ac-mqtt-business:5810/notifyWifiOnlineStatusToHitachi}")
    private String busineseNotifyBaseUrl;


    public AuthService(@Qualifier("base") WebClient webClient, RedisDao redisDao, DevicesService devicesService) {
        this.webClient = webClient;
        this.redisDao = redisDao;
        this.devicesService = devicesService;
    }

    public Mono<ServerResponse> handleAuthClient(ServerRequest req) {
        return req.bodyToMono(String.class)
                .flatMap(r -> {
                    log.info("handleAuthClient: {}", r);
                    if (r.contains("0%2F0%2F999999999999999")) {
                        return ServerResponse.ok().body(Mono.just(true), Boolean.class);
                    } else {
                        return Mono.just(parseMessage(messageFiltering(r)))
                                .zipWhen(authMessage -> authenticateClient(authMessage.getClientId(), authMessage.getUserName(), authMessage.getPassword())
                                        , (x, y) -> y);
                    }
                });
    }


    public String messageFiltering(String oldMsg) {
        return oldMsg.replaceAll("%2B", "+")
                .replaceAll("%20", " ")
                .replaceAll("%2F", "/")
                .replaceAll("%3F", "?")
                .replaceAll("%23", "#")
                .replaceAll("%25", "%")
                .replaceAll("%26", "&")
                .replaceAll("%3D", "=");
    }

    public Mono<Boolean> wifiOnline(String wifiId, String domain, String subDomain, String clientId) {
        return redisDao.set(String.format("%s%s", Constants.ONLINE_REDIS_PREFIX, wifiId), JSONUtil.toJsonStr(
                WifiOnlineInRedisDto.builder()
                        .domain(domain)
                        .subDomain(subDomain)
                        .clientId(clientId)
                        .build()
        ));
    }

    private Mono<ServerResponse> authenticateClient(String clientId, String username, String password) {
        return Mono.just(parseEndpointName(clientId))
                .zipWhen(clientEP -> {
                    if (StringUtils.isNotEmpty(clientEP.getPhysicalID()) && clientEP.getMajorDomainID() == 0 && clientEP.getSubDomainID() == 0 && clientEP.getPhysicalID().equals(Constants.SUPER_ACCOUNT)) {
                        // ignore for super account
                        return ServerResponse.ok().body(Mono.just(true), Boolean.class);
                    } else {
                        if (StringUtils.isEmpty(clientEP.getPhysicalID())) {
                            return ServerResponse.badRequest().body(Mono.just(false), Boolean.class);
                        }
                        return authenticatePassword(clientEP, username, password)
                                .zipWhen(res -> {
                                    if (res) {
                                        return ServerResponse.ok().body(
                                                devicesService.updateDeviceActiveStatusInner(
                                                                Mono.just(UpdateDeviceStatusReq.builder().domain(clientEP.getMajorDomainID()).deviceId(clientEP.getPhysicalID()).build())
                                                        )
                                                        .zipWhen(s -> {
                                                                    if (!clientEP.getPhysicalID().startsWith("86")) {
                                                                        return Mono.zip(
                                                                                wifiOnline(clientEP.getPhysicalID(),
                                                                                        String.valueOf(clientEP.getMajorDomainID()),
                                                                                        String.valueOf(clientEP.getSubDomainID()),
                                                                                        clientEP.getRawName()),
                                                                                notifyWifiOnlineToHitachi(
                                                                                        (int) clientEP.getMajorDomainID(),
                                                                                        (int) clientEP.getSubDomainID(),
                                                                                        clientEP.getPhysicalID()
                                                                                )
                                                                        ).zipWhen(res2 -> Mono.just(true), (m, n) -> n);
                                                                    } else {
                                                                        return wifiOnline(clientEP.getPhysicalID(),
                                                                                String.valueOf(clientEP.getMajorDomainID()),
                                                                                String.valueOf(clientEP.getSubDomainID()),
                                                                                clientEP.getRawName())
                                                                                .zipWhen(res2 -> Mono.just(true), (m, n) -> n);
                                                                    }

                                                                }
                                                                , (x, y) -> y)
                                                , Boolean.class);
                                    }
                                    return ServerResponse.badRequest().body(Mono.just(false), Boolean.class);
                                }, (x, y) -> y);
                    }
                }, (x, y) -> y);
    }

    public Mono<Boolean> notifyWifiOnlineToHitachi(int domain, int subDomain, String wifiId) {
        return webClient.post()
                .uri(busineseNotifyBaseUrl)
                .body(Mono.just(
                        NotifyWifiOnlineToHitachiReq.builder()
                                .majorDomainId(domain)
                                .subDomainId(subDomain)
                                .physicalId(wifiId)
                                .payload(Constants.STATUS_ONLINE_PAYLOAD)
                                .build()
                ), NotifyWifiOnlineToHitachiReq.class)
                .retrieve()
                .bodyToMono(String.class)
                .doOnSuccess(res1 -> {
                    log.info("succeed to notifyWifiOnlineToHitachi {} for wifiId {}", Constants.STATUS_ONLINE, wifiId);
                })
                .doOnError(err -> log.error("succeed to notifyWifiOnlineToHitachi {} for wifiId {}", Constants.STATUS_ONLINE, wifiId, err))
                .zipWhen(s -> Mono.just(s.contains("success")), (x, y) -> y);

    }

    public AuthMessage parseMessage(String rawMessage) {
        Map<String, String> authMessageMap = new HashMap<>();
        Arrays.stream(rawMessage.split("&"))
                .forEach(eachTissue -> {
                    String[] eachTissueKVArray = eachTissue.split("=");
                    if (eachTissueKVArray.length == 2) {
                        authMessageMap.put(eachTissueKVArray[0], eachTissueKVArray[1]);
                    }
                });
        AuthMessage msg = new AuthMessage();
        if (authMessageMap.containsKey("clientid")) {
            msg.setClientId(authMessageMap.get("clientid"));
        }
        if (authMessageMap.containsKey("username")) {
            msg.setUserName(authMessageMap.get("username"));
        }
        if (authMessageMap.containsKey("password")) {
            msg.setPassword(authMessageMap.get("password"));
        }
        return msg;
    }

    // 设备 EndpointName 的格式：<MajorDomainID>/<SubDomainID>/<PhysicalID>_<SecureMode>_<SignMethod>_<Timestamp>
    // example: 2/19/2sthr4rhhi_1_2_1638866152
    // ProductClass 由 domainId/subDomainId 标识
    // SerialNumber是设备的物理ID
    public DeviceEndpointName parseEndpointName(String name) {
        DeviceEndpointName deviceEndpointName = DeviceEndpointName.builder().rawName(name).build();
        Pattern p = Pattern.compile(Constants.ENDPOINT_NAME_PATTHEN);
        Matcher m = p.matcher(name);
        if (m.find()) {
            int totalParamCount = m.groupCount();
            log.info("parseEndpointName->totalParamCount: {}", totalParamCount);
            List<String> allParameters = new ArrayList<>();

            for (int i = 0; i < totalParamCount + 1; i++) {
                allParameters.add(m.group(i));
            }

            deviceEndpointName.setMajorDomainID(Long.parseLong(allParameters.get(1)));
            deviceEndpointName.setSubDomainID(Long.parseLong(allParameters.get(2)));
            deviceEndpointName.setPhysicalID(allParameters.get(3));
            deviceEndpointName.setSecureMode(allParameters.get(4));
            deviceEndpointName.setSignMethod(allParameters.get(5));
            deviceEndpointName.setTimestamp(allParameters.get(6));
        }

        return deviceEndpointName;
    }


    private Mono<Boolean> authenticatePassword(DeviceEndpointName clientEp, String userName, String password) {
        return devicesService.getPublicKeyInner(
                        Mono.just(GetPublicKeyReq.builder()
                                .domain(String.valueOf(clientEp.getMajorDomainID()))
                                .subDomain(String.valueOf(clientEp.getSubDomainID()))
                                .physicalDeviceId(clientEp.getPhysicalID()).build()))
                .zipWhen(p -> {
                    String data = String.format("clientId%stimestamp%s", userName, clientEp.getTimestamp());
                    String signature = "";
                    switch (clientEp.getSignMethod()) {
                        case Constants.ClientEp.SignMethod.HmacMD5 -> {
                            signature = HmacUtils.hmacMd5Hex(p.getPublicKey().getBytes(StandardCharsets.UTF_8), data.getBytes(StandardCharsets.UTF_8));
                        }
                        case Constants.ClientEp.SignMethod.HmacSHA1 -> {
                            signature = HmacUtils.hmacSha1Hex(p.getPublicKey().getBytes(StandardCharsets.UTF_8), data.getBytes(StandardCharsets.UTF_8));
                        }
                        case Constants.ClientEp.SignMethod.HmacSHA256 -> {
                            signature = HmacUtils.hmacSha256Hex(p.getPublicKey().getBytes(StandardCharsets.UTF_8), data.getBytes(StandardCharsets.UTF_8));
                        }
                    }
                    log.info("authenticatePassword-> signature: {}, password: {}", signature, password);
                    return Mono.just(signature.equals(password));
                }, (x, y) -> y);
    }

    public static String calcutePassword(String clientId, String timestamp, String preKey) {
        return HmacUtils.hmacSha1Hex(preKey.getBytes(StandardCharsets.UTF_8), String.format("clientId%stimestamp%s", clientId, timestamp).getBytes(StandardCharsets.UTF_8));
    }
}









































